﻿using Cuofrisp.Core;
using Cuofrisp.Core.ParallelCore.Interfaces;
using MathNet.Symbolics;

namespace Cuofrisp.Console.MetricInverseProviders
{
    internal sealed class NewBingGeneratedMetricInverseProvider : IMetricInverseProvider
    {
        private static bool IsZero(SymbolicExpression expression)
        {
            expression = expression.TrigonometricSimplify().ExponentialSimplify();
            if (expression.Type is not SymbolicExpressionType.RationalNumber)
                return false;
            return expression.RationalNumberValue.IsZero;
        }

        /// <summary>
        /// Generated by new Bing (but for double[,] originally).
        /// </summary>
        /// <param name="matrix"></param>
        /// <param name="simplifier"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private static SymbolicExpression[,] Inverse(
            SymbolicExpression[,] matrix, IExpressionSimplifier simplifier)
        {
            int n = matrix.GetLength(0);
            SymbolicExpression[,] result = new SymbolicExpression[n, n];
            SymbolicExpression[,] temp = new SymbolicExpression[n, 2 * n];

            // Copy input matrix into temp
            for (int i = 0; i < n; ++i)
                for (int j = 0; j < n; ++j)
                    temp[i, j] = matrix[i, j];

            // Augment temp with identity matrix
            for (int i = 0; i < n; i++)
            {
                for (int j = n; j < 2 * n; j++)
                {
                    temp[i, j] = SymbolicExpression.Zero;
                }
            }
            for (int i = 0; i < n; ++i)
                temp[i, i + n] = SymbolicExpression.One;

            // Perform row operations
            for (int i = 0; i < n; ++i)
            {
                // Swap rows if necessary
                if (IsZero(temp[i, i]))
                {
                    int swapRow = i + 1;
                    while (swapRow < n && IsZero(temp[swapRow, i]))
                        ++swapRow;

                    if (swapRow == n)
                        throw new Exception("Matrix is singular");

                    for (int j = 0; j < 2 * n; ++j)
                    {
                        var tempValue = temp[i, j];
                        temp[i, j] = temp[swapRow, j];
                        temp[swapRow, j] = tempValue;
                    }
                }

                // Scale row
                var scaleValue = temp[i, i];
                for (int j = 0; j < 2 * n; ++j)
                    temp[i, j] /= scaleValue;

                // Eliminate other rows
                for (int j = 0; j < n; ++j)
                {
                    if (i == j)
                        continue;

                    var eliminateValue = temp[j, i];
                    for (int k = 0; k < 2 * n; ++k)
                        temp[j, k] -= eliminateValue * temp[i, k];
                }
            }

            // Copy result into output matrix
            for (int i = 0; i < n; ++i)
                for (int j = 0; j < n; ++j)
                    result[i, j] = simplifier.Simplify(temp[i, j + n]);

            return result;
        }

        public void TrySetMetricInverse(
            CuofrispResult result, IExpressionSimplifier simplifier, out bool isNotFullRank)
        {
            try
            {
                Array.Copy(
                    Inverse(result.Metric, simplifier),
                    result.MetricInverse,
                    result.VariableCount * result.VariableCount);
                isNotFullRank = false;
            }
            catch
            {
                isNotFullRank = true;
            }
        }
    }
}
